// Scanner.java - the scanner.

import java.util.*;

public class Scanner {

    private String program;
    private int pos; // current position
    private Token token;
    private Set<String> whitespace=new HashSet<String>();
    private Set<String> digits=new HashSet<String>();
    private Set<String> letters=new HashSet<String>();
    private Set<String> legits=new HashSet<String>();
    private Set<String> keywords=new HashSet<String>();
    private Set<String> operators=new HashSet<String>();
    private Set<String> comments = new HashSet<String>(); // '['
    private Set<String> decimal=new HashSet<String>(); // '.'

    /**
     * Used to fill a set with a consequent number of chars.
     * @param s - the set to be filled.
     * @param lo - the starting char
     * @param hi - the ending char
     */
    private void fill(Set<String> s, char lo, char hi) {
		for (char c=lo; c<=hi; c++)
		    s.add(c+"");
    }    

    /**
     * Initialize the set of whitespace.
     * @param s
     */
    private void initWhitespace(Set<String> s) {
		s.add(" ");
		s.add("\n");
		s.add("\t");
    }

    /**
     * initialize the set of digits.
     * @param s
     */
    private void initDigits(Set<String> s) {
    	fill(s,'0','9');
    }

    /**
     * Initialize the set of letters.
     * @param s
     */
    private void initLetters(Set<String> s) {
		fill(s,'A','Z');
		fill(s,'a','z');
    }

    /**
     * Initialize the set of legits. This includes both letters and digits.
     * @param s
     */
    private void initLegits(Set<String> s) {
		s.addAll(letters);
		s.addAll(digits);
    }

    /**
     * Initialize the set of operators.
     * @param s
     */
    private void initOperators(Set<String> s) {
		s.add("=");
		s.add("+");
		s.add("-");
		s.add("*");
		s.add("/");
		s.add("(");
		s.add(")");
		s.add(";");
		// adding relops.
		s.add("<");
		s.add("<=");
		s.add(">");
		s.add(">=");
		s.add("<>");
		s.add("==");
    }

    /**
     * Initialize the set of keywords.
     * @param s
     */
    private void initKeywords(Set<String> s) {
    	s.add("rd");
    	s.add("wr");
    	s.add("if");
    	s.add("then");
    	s.add("else");
    	s.add("while");
    	s.add("do");
    	s.add("begin");
    	s.add("end");
    }
    
    /**
     * Initialize the set of comment sign.
     * @param s
     */
    private void initCommentStarts(Set<String> s) {
    	s.add("[");
    }

    /**
     * Initialize the set of decimal.
     * @param s
     */
    private void initDecimal(Set<String> s) {
    	s.add(".");
    }
    
    /**
     * Constructor.
     * @param program
     */
    public Scanner(String program) {
		this.program=program;
		pos=0;
		token=null;
		initWhitespace(whitespace);
		initDigits(digits);
		initLetters(letters);
		initLegits(legits);
		initCommentStarts(comments); // Comments sign.
		initOperators(operators);
		initDecimal(decimal);
		initKeywords(keywords); // keywords.
    }

    /**
     * Tell if we run beyond the end of the string.
     * @return
     */
    public boolean done() {
    	return pos>=program.length();
    }

    /**
     * Jump to the next char that is not in the set, if the current char is in the set.
     * Used to find consecutive chars of the same type. 
     * @param s
     */
    private void many(Set<String> s) {
		while (!done() && s.contains(program.charAt(pos)+""))
		    pos++;
    }
    
    /**
     * Jump to the position right after the next given char.
     * @param c
     */
    private void past(char c) {
		while (!done() && c!=program.charAt(pos))
		    pos++;
		if (!done() && c==program.charAt(pos))
		    pos++;
    }

    /**
     * Find the next number and make it a token.
     */
    private void nextNumber() {
		int old=pos;
		many(digits);
		if (!done()) {
			if(program.charAt(pos)=='.') {
				passDecimal();
				many(digits);
			}
		}
		token=new Token("num",program.substring(old,pos));
    }

    /**
     * Find the next id (letters+numbers) and make it a token.
     */
    private void nextKwId() {
		int old=pos;
		many(letters);
		many(legits);
		String lexeme=program.substring(old,pos);
		// Check if this id is a keyword.
		token=new Token((keywords.contains(lexeme) ? lexeme : "id"),lexeme);
    }

    /**
     * Find the next operator/relop and make it a token.
     */
    private void nextOp() {
		int old=pos;
		pos=old+2; // an op of length=2
		if (!done()) {
		    String lexeme=program.substring(old,pos);
		    if (operators.contains(lexeme)) {
				token=new Token(lexeme);
				return;
		    }
		}
		pos=old+1; // an op of length=1
		String lexeme=program.substring(old,pos);
		token=new Token(lexeme);
    }

    /**
     * Find the next comment.
     */
    private void nextCmnt() {
    	int old=pos;
    	past(']');
    	token = new Token("cmnt",program.substring(old,pos));
    }
    
    /**
     * Pass the decimal sign.
     */
    private void passDecimal() {
    	pos++;
	}
    
    /**
     * Find the next valid token and make a token.
     * @return false if done.
     */
    public boolean next() {
		if (done())
		    return false;
		//System.out.println(pos);
		many(whitespace); // skip white spaces.
		String c=program.charAt(pos)+"";
		if (digits.contains(c))
		    nextNumber();
		else if (letters.contains(c))
		    nextKwId();
		else if (operators.contains(c))
		    nextOp();
		else if (comments.contains(c)) 
			nextCmnt();
		else if (decimal.contains(c))
			passDecimal();
		else {
		    System.err.println("illegal character at position "+pos);
		    pos++;
		    return next();
		}
		return true;
    }

	/**
     * See if the current token matches the token provided.
     * If not, throws SyntaxException; if yes, find the next token.
     * @param t
     * @throws SyntaxException
     */
    public void match(Token t) throws SyntaxException {
		if (!t.equals(curr()))
		    throw new SyntaxException(pos,t,curr());
		next();
    }

    /**
     * Return current token.
     * @return
     * @throws SyntaxException
     */
    public Token curr() throws SyntaxException {
		if (token==null)
		    throw new SyntaxException(pos,new Token("ANY"),new Token("EMPTY"));
		return token;
    }

    /**
     * Skip comment.
     * @throws SyntaxException
     */
	public void skipCmnt() throws SyntaxException {
		while(curr().equals(new Token("cmnt")))
			next();
	}
    
	/**
     * Show current position.
     * @return
     */
    public int pos() { return pos; }

    /**
     * Main method. - show the parsing tokens of the input.
     * @param args
     */
    public static void main(String[] args) {
    	String str = ""; // concatenate arguments as 1 string.
		for (String stmt : args) {
			str += " ";
			str += stmt;
		}
		try {
			Scanner scanner = new Scanner(str);
			while (scanner.next())
				System.out.println(scanner.curr());
		} catch (SyntaxException e) {
			System.err.println(e);
		}
    }

}
